/** @file    SystemMonitor.cpp
 *  @author  Alessandro Polo
 *  @version $Id: SystemMonitor.cpp 2832 2010-08-02 22:53:46Z alex $
 *  @brief
 * File containing methods for the wosh::SystemMonitor class.
 * The header for this class can be found in SystemMonitor.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/
 
 #include <core/SystemMonitor.h>

 #include <core/ObjectAllocator.h>
 #include <core/WoshKernel.h>
 #include <core/PersistenceManager.h>
 #include <core/SecurityManager.h>
 #include <core/SharedMemory.h>
 #include <core/MethodsCommon.h>
 #include <core/FilePath.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/Functional.h>
 #include <core/Utilities.h>
 #include <core/DataModels.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef _OS_POSIX
# include <core/posix/processes.h>
# include <core/posix/desktop.h>
#elif _OS_WIN32
# include <core/win32/processes.h>
# include <core/win32/desktop.h>
#elif _OS_WINCE
# include <core/wince/processes.h>
# include <core/wince/desktop.h>
#else
 long getCurrentProcessPID() { return 0L; }
 bool getSystemUserName( std::string& ) { return false; }
 bool retrieveProcessInfo( long , std::map<std::string, std::string>& ) { return false; }
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

using namespace std;
namespace wosh {

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Gamma singleton! static reference is allocated and maintained by WoshKernel
extern "C" {
	SystemMonitor* SystemMonitor::getInstance() { 
		if ( WoshKernel::getInstance() == NULL ) return NULL;
		return &WoshKernel::getInstance()->monitor();
	}
};

 // register as abstract because it is a singleton:
 WOSH_REGISTER_INTERFACE(wosh::SystemMonitor, "wosh::WoshModule", 0.01, _static_SystemMonitor )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

SystemMonitor::SystemMonitor( const WoshKernel* parent )
	: WoshModule(parent, _SystemMonitor_NAME, _SystemMonitor_URI) {

//Log.setLevel( LOG_VERBOSE );

#ifdef _ENABLE_PROFILING
	Log(LOG_INFO, " Acquiring SystemProfiler instance.. [_ENABLE_PROFILING]" );
	this->profiler = &SystemProfiler::getInstance();
#else
	Log(LOG_VERBOSE, " Profiling DISABLED." );
#endif

	Log(LOG_DEBUG, " Initializing Properties.." );
	Properties.set(_SystemMonitor_KEY_SaveOnExit, false, Permission(Permission::RW, Permission::Read, Permission::Read) );
	Properties.set(_SystemMonitor_KEY_ProcessPID, 0, Permission(Permission::Read) );
	Properties.set(_SystemMonitor_KEY_SystemUser, "", Permission(Permission::Read) );

	Log(LOG_DEBUG, " Registering Methods.." );
	MethodRequest* mmProfilesList = Methods.create<MethodRequest>( _SystemMonitor_METHOD_profiles, "list profiling info", Permission::R_XR_XR__() );
	mmProfilesList->setMethod( this, (MethodRequestPtr)&SystemMonitor::mmDoProfilesList );

	MethodRequest* mmListDeprecated = Methods.create<MethodRequest>( _SystemMonitor_METHOD_deprecated, "list deprecated", Permission::R_XR_XR__() );
	mmListDeprecated->setMethod( this, (MethodRequestPtr)&SystemMonitor::mmDoListDeprecated );

	MethodWRESULT* mmSaveErrors = Methods.create<MethodWRESULT>( _SystemMonitor_METHOD_save_errors, "save errors", Permission::R_XR_XR__() );
	mmSaveErrors->setMethod( this, (MethodWRESULTPtr)&SystemMonitor::mmDoSaveErrors );

	MethodWRESULT* mmCleanErrors = Methods.create<MethodWRESULT>( _SystemMonitor_METHOD_clear_errors, "clean errors", Permission::R_XR_XR__() );
	mmCleanErrors->setMethod( this, (MethodWRESULTPtr)&SystemMonitor::mmDoCleanErrors );

	MethodWRESULT* mmUpdateSysInfo = Methods.create<MethodWRESULT>( _SystemMonitor_METHOD_update_sysinfo, "update sysinfo", Permission::R_XR_XR__() );
	mmUpdateSysInfo->setMethod( this, (MethodWRESULTPtr)&SystemMonitor::updateSystemInfo );

	MethodList* mmListErrors = Methods.create<MethodList>( _METHOD_List, "List Errors", Permission::R_XR_XR__() );
	mmListErrors->setDataFieldExtractor( DataFieldExtractor<SystemError>::createFor< DataAdapterListT<SystemError> >(this->errorLog, "System Errors") );

	MethodRetrieve* mmRetrieveErrors = Methods.create<MethodRetrieve>( _METHOD_Retrieve, "Retrieve Error(s)", Permission::R_XR_XR__() );
	mmRetrieveErrors->setItemExtractor( ObjectExtractor<SystemError, int64>::createFor< DataAdapterListT<SystemError> >(this->errorLog, &SystemError::getID ) );

	MethodList* mmListDSMs = Methods.create<MethodList>( _SystemMonitor_METHOD_dsm, "List DSM", Permission::R_XR_XR__() );
	mmListDSMs->setDataFieldExtractor( DataFieldExtractor<SharedMemory>::createFor< DataAdapterListT<SharedMemory> >(SharedMemory::getInstances(), "Shared Memories") );

#ifdef _ENABLE_PROFILING
	this->profiler = &SystemProfiler::getInstance();
#endif
 }

SystemMonitor::~SystemMonitor() {
	Log(LOG_DEBUG, ":~SystemMonitor() Destroying.." );
	Log(LOG_VERBOSE, ":~DeviceManager() Freeing ErrorLog [%d]..", this->errorLog.count() );
	this->errorLog.clear();
	// this->profiler is destroyed by compiler because is a static singleton (meyer)
	Log(LOG_VERBOSE, " Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS

WRESULT SystemMonitor::onKernelInitializing() {
	WRESULT ret = WoshModule::onKernelInitializing();

	long pid = getCurrentProcessPID();
	Properties.transactionBeginWrite();
	Properties.update(_SystemMonitor_KEY_ProcessPID, pid );
	Properties.transactionEnd();
	Log(LOG_INFO, "init() : PID updated to %ld", pid );
	ret += updateSystemInfo_(true, true);

	return ret;
 }

WRESULT SystemMonitor::onKernelStopped() {
	WRESULT ret = WRET_OK;
	Variant saveMsg = this->Properties.getValueOf(_SystemMonitor_KEY_SaveOnExit);
	if ( !saveMsg.isEmpty() && saveMsg.toBoolean(true) ) {
		Log(LOG_INFO, "onKernelStopped() Saving Errors.." );
		ret += mmDoSaveErrors();
	 }
	if ( WFAILED(ret) )
		Log(LOG_CRITICAL, ":onKernelStopped() ERROR#%d Saving Errors.", ret );
	ret = WoshModule::onKernelStopped();
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////////// KERNEL EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool SystemMonitor::readingProperty( Property* property_curr, const PropertiesProvider* source ) {
	if ( property_curr != NULL && property_curr->getKey().find("SysInfo") == 0 )
		updateSystemInfo_(false, false);
	return WoshModule::readingProperty( property_curr, source );
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT SystemMonitor::updateSystemInfo_( bool lock_props, bool force_update ) {
	 if ( !force_update ) {
		if ( Utilities::std_time() - this->systemInfoTs < _SystemMonitor_SystemInfoMinRefresh )
			 return WRET_ERR_ILLEGAL_USE;
	  }
	this->systemInfoTs = Utilities::std_time();
	Log(LOG_VERBOSE, "updateSystemInfo_(%d) Updating System Info..", lock_props );

	string username;
	bool userOk = getSystemUserName(username);
	if ( !userOk ) {
		Log(LOG_CRITICAL, "updateSystemInfo_(%d) Error retrieving system user", lock_props );
	 }
	long pid = getCurrentProcessPID();

	Properties.transactionBeginWrite();
	Properties.update(_SystemMonitor_KEY_SystemUser, username);
	Properties.transactionEnd();

	///@todo use Variant, so data is TYPED
	map<string, string> fields;
	bool memInfo = retrieveProcessInfo( pid, fields );
	if ( memInfo ) {
		Properties.transactionBeginWrite();
		std::map<string, string>::const_iterator it;
		for ( it=fields.begin(); it!=fields.end(); ++it ) {
			Properties.update(it->first, it->second);
		 }
		Properties.transactionEnd();
	 }
	else {
		Log(LOG_CRITICAL, "updateSystemInfo_() ERRORS retrieving process info." );
		return WRET_ERR_INTERNAL;
	 }
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// COLLECTOR METHODS

WRESULT SystemMonitor::mmDoSaveErrors() {
	FilePath xmlFile;
	xmlFile.set("$DATABASE/systemerrors.xml");
	Log(LOG_VERBOSE, ":mmDoSaveErrors() : Saving to %s", xmlFile.getPathLocal().c_str() );
	this->errorLog.transactionBeginRead();
	WRESULT ret = PersistenceManager::saveObjects( this->errorLog.getVector(), "XML", xmlFile.getPathLocal() );
	this->errorLog.transactionEnd();

/*
//	PersistenceDbT<SystemError> pDb;
//	pDb.

	PersistenceDb* db = PersistenceManager::createDb("XML");

//db->binding( this->errorLog );
	DataAdapterListT<SystemError>* errorsDataAdapter = new DataAdapterListT<SystemError>(this->errorLog);
	db->setDataAdapter(errorsDataAdapter);

	db->getProperties().set("filename", "$DATABASE/systemerrors.xml" );
	db->save();
*/
	return ret;
 }

/////////////////////////////////////////////////////////////////////////////////////////// COLLECTOR METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////// 

void SystemMonitor::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	WoshModule::busMessage( message, source );

	if ( message.getSourceUUID() == this->getEntityID() ) return;
	if ( !message.getContent()->isSystemError() ) return;

	const SystemError* sysError = message.getContent()->asSystemError();
	Log(LOG_INFO, ":busMessage() : Adding Error#%"PRId64, sysError->getID() );
	this->errorLog.transactionBeginWrite();
	this->errorLog.push_back( sysError->clone() );
	this->errorLog.transactionEnd();
	return;
 }

////////////////////////////////////////////////////////////////////////////////////////// THREADs OPERATIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

WRESULT SystemMonitor::mmDoCleanErrors() {
	Log(LOG_INFO, ":mmCleanErrors() Cleaning.. [%d records]", this->errorLog.size() );
	this->errorLog.free();
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* SystemMonitor::mmDoProfilesList( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant target; Variant option1;
	request->extractArguments(target, option1);
	if ( !target.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "specify target {properties, methods, connectors}");
	Log(LOG_VERBOSE, ":mmDoProfilesList(%s)", target.asString().c_str() );
#ifdef _ENABLE_PROFILING
	if ( this->profiler == NULL )
		return request->replyResponse(WRET_ERR_INTERNAL, "Internal error: Profiler is NULL");

	if ( target.asString() == "properties" )
		return request->replyResponse(WRET_OK, this->profiler->getProfilesPropertiesTable(option1.toInt64(1)) );
	else if ( target.asString() == "methods" )
		return request->replyResponse(WRET_OK, this->profiler->getProfilesMethodsTable(option1.toInt64(0)) );
	else if ( target.asString() == "connectors" )
		return request->replyResponse(WRET_OK, this->profiler->getProfilesConnectorsTable(option1.toInt64(0)) );
#else
	return request->replyResponse(WRET_ERR_ILLEGAL_USE, "Profiling is not enabled in this built.");
#endif
	return request->replyResponse(WRET_ERR_PARAM, "specify valid target {properties, methods, connectors}");
 }

Response* SystemMonitor::mmDoListDeprecated( const Request* request ) {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoListDeprecated(%s)", option.c_str() );

												WOSH_DEPRECATE( "SystemMonitor::mmDoListDeprecated", "SAMPLE" )

#ifdef _ENABLE_PROFILING
	if ( this->profiler == NULL )
		return request->replyResponse(WRET_ERR_INTERNAL, "Internal error: Profiler is NULL");
	return request->replyResponse(WRET_OK, this->profiler->getDeprecatedTable() );
#else
	return request->replyResponse(WRET_ERR_ILLEGAL_USE, "Deprecated Calls logging is not enabled in this built.");
#endif
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS

WRESULT SystemMonitor::raiseError( SystemError* error, const Object* source ) {
	if (SystemMonitor::getInstance() == NULL) return WRET_ERR_INTERNAL;
	Message* msg_system = new Message(error);
	msg_system->setSource( source );
	msg_system->setDestinationBroadcast(false);
	msg_system->setDestinationBus(_Bus_Core);
	SecurityManager::getCredentialImplicit().signMessage(msg_system, source);
	SystemMonitor::getInstance()->BusCore.postMessage(msg_system);
	return WRET_OK;
 }

////////////////////////////////////////////////////////////////////////////////////////////// STATIC METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
